/*
 * This class is set up with a handfull of transitions (addTransitions) which create
 * a Finite State Machine table in a hashmap internally.
 * 
 * Once set up, events will drive state transitions through the table.
 * 
 * For instance, if you wanted to use keystrokes to drive a state machine that
 * would only allow you to type the word "bill", your state table might look like
 * this:
 * 
 *                          States:
 *             starting   b    bi   bil   bill (ending state)
 * events    |-----------------------------------|
 *     b     |    >b      x    x    x      x     |
 *           |-----------------------------------|
 *     i     |    x      >bi   x    x      x     |
 *           |-----------------------------------|
 *     l     |    x       x  >bil  >bill   x     |
 *           |-----------------------------------|
 * 
 * To set this up, you would call addTransaction with the following 4 calls:
 * 
 * addTransaction(state_starting, event_b, state_b)
 * addTransaction(state_b,        event_i, state_bi)
 * addTransaction(state_bi,       event_l, state_bil)
 * addTransaction(state_bil,      event_l, state_bill)
 * 
 * After that setup, the state engine will run on its own, handling notifications
 * from its events and sending actions to its states.
 * 
 * Event queueing is allowed, even when in an event.  Events will be processed
 * in order, and validity will always be enforced.
 * 
 * If an "X" state is reached, the engine will either ignore it or throw an
 * exception to the event causing the error. (Depending on a flag setting.)
 * 
 * It's very important that the addTransaction statements are all in one
 * area and use good names.  This is your state machine documentation!
 *
 */
package states;

import java.util.Hashtable;
import java.util.LinkedList;


/**
 * 
 * @author billkress
 */
public class StateEngine {
    public final static Object STATE_DO_NOTHING=null;

    /** Holds all possible transactions for quick lookup */
    private final StateTableWithNestedHashtables states = new StateTableWithNestedHashtables();
    /** The current state of this machine, can't be null */
    private IState currentState;
    /** the name for logging purposes */
    private final String name;
    /** Event Queue */
    private final LinkedList events = new LinkedList();
    /**
     * Variable to allow state machine to be used as validator by simply
     * ignoring invalid state transitions.
     */
    private boolean exceptOnIllegal = true;

    /**
     * The firstState's entryAction will NOT be called since there is not an
     * event that triggered it. The exitAction will be called.
     * 
     * @param firstState
     *            Mostly a placeholder--the initial state of the machine.
     * @param name
     *            Used for logging
     */
    public StateEngine(String name, IState firstState) {
        this.name = name;
        if (firstState == null) {
            throw new IllegalArgumentException();
        }
        currentState = firstState;
    }

    /**
     * This version of the constructor isn't bulletproof, if you feed it a
     * malformed table you can confuse it.
     * 
     * As coded, all the nulls at the end of any row can be left off.
     * 
     * If you use a matrix format to initialize the table, I recomend you
     * comment it out before auto-reformatting the file, then remove the
     * comments (select the area then ctrl-slash for most IDEs I think)
     * 
     * @param name
     * @param stateTable
     */
    public StateEngine(String name, Object[] stateTable) {
        this.name = name;
        int colNo = 0;

        IEvent readingEvent = null;
        currentState = (IState) stateTable[0];

        // The current node being read
        int i = 0;

        // Skip over the state headers at the top
        while (stateTable[i] instanceof IState) {
            i++;
        }

        // Make sure the first thing after loading the table is the event column
        if (!(stateTable[i] instanceof IEvent)) {
            throw new IllegalArgumentException();
        }

        // Now, starting from the first event in the event column, read each row
        for (; i < stateTable.length; i++) {
            Object cell = stateTable[i];

            // Switch to next row when we hit an event
            if (cell instanceof IEvent) {
                readingEvent = (IEvent) cell;
                colNo = -1;
                continue;
            }
            // Keep a running count of what column of the row we are on so we
            // can find a source event
            // (index into the top row of the table.) A nice check to add here
            // would be to make sure
            // it is never past the first event we saw (always in the top row)
            colNo++;

            // Ignore nulls, but they still count as columns
            if (cell == null) {
                continue;
            }

            // Just an easy sanity check
            if (!(cell instanceof IState)) {
                throw new IllegalArgumentException("Item found in table not IState, IStateEvent or null, itemno:" + i);
            }

            // We have identified a "next state" in the node, a "starting state"
            // in the
            // header at the top of this column, and a transition (the last
            // event we hit)
            // Add the transition.
            addTransition((IState) stateTable[colNo], readingEvent, (IState) cell);
        }
    }

    /**
     * programs the engine with the allowable transactions.
     * 
     * The engine is always in a state. when an event goes off, this table is
     * queried to see if the event is valid for the current state, if so the
     * current state changes to the nextState and all the appropriate state
     * actions are called.
     * 
     * @param startingState
     *            if the engine is in this state
     * @param ev
     *            and this event activates
     * @param nextState
     *            move to this state
     */
    public void addTransition(IState startingState, IEvent ev, IState nextState) {
        ev.setStateEventListener(this);
        System.out.println(this + "Adding a transition from " + startingState + " > " + ev + " = " + nextState);
        states.addTransition(startingState, ev, nextState);
    }

    /**
     * True by default, if this is set to false, bad events will simply be
     * ignored. This is to be used in cases where events will come all the time
     * and the StateEngine is used to discard events we don't want at the
     * time--makes it an active filter.
     * 
     * Note, unless you ABSOLUTELY INTEND to use the state machine as a filter,
     * you really should leave this variable on the default "true" or you
     * violate the "Fail-Fast Fail-Often" principle.
     * 
     * @param except
     *            false to stop exceptions.
     */
    public void setExceptionOnIllegalTransiction(boolean except) {
        this.exceptOnIllegal = except;
    }

    /**
     * This is what actually causes state transitions.
     * 
     * It queues events, takes the first event, makes sure it's valid, calls the
     * current state's exitAction, changes state, and calls the next state's
     * entryAction.
     * 
     * If a state change is not valid, it throws an "IllegalStateException"
     * containing the names of the start state, end state and event.
     * 
     * Should handle multiple events and threading situations fine. Events are
     * handled in the order they are received and state transitions are ALWAYS
     * enforced.
     * 
     * This method is usually called by the default implementation of
     * Event.fireStateEvent()
     * 
     * @param ev
     *            The event that fired.
     */
    public void fireStateEvent(IEvent ev) {
        synchronized (events) {
            events.add(ev);
            // If there are more events, they will be executed after the current
            // one is done, so just return the thread to whoever sent it.
            if (events.size() != 1) {
                return;
            }

        }
        while (true) {

            synchronized (events) {
                ev = (IEvent) events.getFirst();
            }

            IState tmp = states.findTransition(currentState, ev);

            // No valid state to transition to
            if (tmp == null) {
                // Have to wipe out the queue or this thing will lock up.
                // Plus at this point we are in an illegal state anyway and
                // cannot continue.
                synchronized (events) {
                    events.clear();
                }

                if (exceptOnIllegal) {
                    throw new IllegalStateException(this + " can not accept event \"" + ev + "\" when in state \"" + currentState + "\"");
                }

            } else {

                System.out.println(this + " transition from state \"" + currentState + "\" to state \"" + tmp + "\" because of event \"" + ev + "\"");

                if (currentState != null) {
                    currentState.exitAction(ev);
                }

                currentState = tmp;

                if (setListener != null) {
                    setListener.newState(currentState, ev);
                }

                IEvent next = currentState.entryAction(ev);

                synchronized (events) {
                    if (next != null) {
                        events.add(next);
                    }

                }
            }
            synchronized (events) {
                events.removeFirst();
                if (events.isEmpty()) {
                    return;
                }
            }
        }
    }

    /**
     * Name for trace purposes. FSM=Finite State Machine
     * 
     * @return
     */
    public String toString() {
        return "[FSM " + name + "]";
    }
    // TODO: add support for multiple listeners.
    private IStateEngineTransitionListener setListener;

    public void addStateEngineTransactionListener(IStateEngineTransitionListener testEventsMatrix) {
        this.setListener = testEventsMatrix;
    }
}
/**
 * Top level hashtable indexed by event objects. each is (event, hashtable) the
 * internal hashtable is (startingState, nextState)
 * If "startingState" is the string "Default", it means to use this event for all startingStates
 * If nextState is "EMPTY" it means to block a default event from this startingState
 * 
 * This can be implemented a few ways.  If we were to gaurentee that Events and States
 * had unique .toStrings() then we could use a lookup like Event.toString()+"+"+State.toString()
 * 
 * That's why this module is extracted -- it's replaceable.
 */
class StateTableWithNestedHashtables {

    private final Hashtable states = new Hashtable();

    public void addTransition(IState startingState, IEvent ev, IState nextState) {
        Hashtable h = (Hashtable) states.get(ev);
        if (h == null) {
            h = new Hashtable(3);
            states.put(ev, h);
        }

        Object next;
        if (nextState != null) {
            next = nextState;
        } else {
            next = "EMPTY";
        }

        if (startingState == null) {
            h.put("Default", next);
        } else {
            h.put(startingState, next);
        }
    }

    public IState findTransition(IState currentState, IEvent ev) {
        Hashtable h = (Hashtable) states.get(ev);
        // Never seen this state.  So how did we even get the event?
        if (h == null) {
            return null;
        }

        Object next = h.get(currentState);
        // Next can now be any of null, "EMPTY" or a new state
        if (next == null) {
            return (IState) h.get("Default");
        }

        if (next.equals("EMPTY")) {
            return null;
        }

        return (IState) next;
    }
}

